package com.zch.starter.opentelemetry.code.mode.springboot.starter.config;


import com.zch.starter.opentelemetry.code.mode.springboot.starter.properties.ZchOpentelemetryProperties;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.instrumentation.spring.webmvc.v5_3.SpringWebMvcTelemetry;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.Filter;
import java.util.concurrent.TimeUnit;

/**
 * 类执行顺序：ConditionalOnProperty条件满足-->spring创建SentinelNacosAutoConfiguration实例--》spring
 * 创建SentinelNacosAutoConfigProperties-->spring 注入成员变量sentinelNacosAutoConfigProperties--》@PostConstruct
 * 方法执行--》@Bean方法执行
 */
@ConditionalOnProperty(prefix = "otel", name = "autoconfig.enabled", havingValue = "true", matchIfMissing = false)
@EnableConfigurationProperties(value = {ZchOpentelemetryProperties.class})
public class OpentelemetryAutoConfiguration {
    static {
        // 在类加载时尽早设置(没有用 仍晚于 Agent 初始化，要避免使用javaagent 不然会报冲突异常)
       // System.setProperty("otel.javaagent.enabled", "false");
    }

    @Resource
    ZchOpentelemetryProperties zchOpentelemetryProperties;
    //spring 的环境上下文类
    @Resource
    Environment environment;

    /**
     * @PostConstruct 是指spring 创建好bean后 且属性被注入赋值结束后 才执行，他比里的@Bean 方法先执行
     *
     *
     * 属性设置完后 设置系统变量效果跟启动命令 JVM -D 一样的
     * 也可以在启动命令里做：https://github.com/alibaba/Sentinel/wiki/%E5%90%AF%E5%8A%A8%E9%85%8D%E7%BD%AE%E9%A1%B9
     * -Dproject.name=client-biz-test # 指定当前客户端的名称
     * -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 # 指定远程的dashoard的地址。
     */
    @Deprecated// 没有用的，因为opentelemetry 的java agent 会在spring 容器初始化之前就读取了系统属性并完成了初始化
    @PostConstruct
    public void init() {
      /*  // 效果跟启动命令方式一样( -Dproject.name=***)
        System.setProperty("otel.traces.exporter", zchOpentelemetryProperties.getTraces().getExporter());
        System.setProperty("otel.metrics.exporter", zchOpentelemetryProperties.getMetrics().getExporter());
        System.setProperty("otel.logs.exporter", zchOpentelemetryProperties.getLogs().getExporter());
        System.setProperty("otel.service.name", zchOpentelemetryProperties.getService().getName());
        System.setProperty("otel.exporter.otlp.endpoint",zchOpentelemetryProperties.getExporter().getEndpoint());
        System.setProperty("otel.traces.sampler", zchOpentelemetryProperties.getTraces().getSampler());
        System.setProperty("otel.traces.sampler.arg", String.valueOf(zchOpentelemetryProperties.getTraces().getSamplerConfig().getArg()));*/

    }

    /**
     * 手动创建OpenTelemetry， 用了这种就不能使用javaagent了，不然会报GlobalOpenTelemetry 异常
     * 没有用 仍晚于 Agent 初始化，要避免使用javaagent 不然会报冲突异常
     * @return
     */
    @Bean
    public OpenTelemetry openTelemetry() {
        // 1. 手动构建导出器和处理器（完全绕过自动配置）
        SpanExporter exporter = OtlpGrpcSpanExporter.builder()
                .setEndpoint(zchOpentelemetryProperties.getExporter().getEndpoint())
                .build();

        SpanProcessor processor = BatchSpanProcessor.builder(
                        new LoggingSpanExporterWrapper(exporter, zchOpentelemetryProperties.getExporter().getEndpoint())
                )
                .setScheduleDelay(100, TimeUnit.MILLISECONDS)
                .build();

        SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
                .addSpanProcessor(processor)
                .setSampler(Sampler.traceIdRatioBased(
                        zchOpentelemetryProperties.getTraces().getSamplerConfig().getArg()
                ))
                .build();

        // 一次性完成全局注册
        OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
                .setTracerProvider(tracerProvider)
                .buildAndRegisterGlobal();

        return openTelemetry;

       /* OpenTelemetry openTelemetry = AutoConfiguredOpenTelemetrySdk.builder()
                .setResultAsGlobal() // 设置为全局实例,这个会跟-javaagent opentelemetry启动命令冲突，用这个编程式的就要完全去掉 Java Agent
                .addPropertiesSupplier(() -> {
                    Map<String, String> config = new HashMap<>();
                    config.put("otel.traces.exporter", zchOpentelemetryProperties.getTraces().getExporter());
                    config.put("otel.metrics.exporter", zchOpentelemetryProperties.getMetrics().getExporter());
                    config.put("otel.logs.exporter", zchOpentelemetryProperties.getLogs().getExporter());
                    config.put("otel.service.name", zchOpentelemetryProperties.getService().getName());
                    config.put("otel.exporter.otlp.endpoint",zchOpentelemetryProperties.getExporter().getEndpoint());
                    config.put("otel.traces.sampler", zchOpentelemetryProperties.getTraces().getSampler());
                    config.put("otel.traces.sampler.arg",
                            String.valueOf(zchOpentelemetryProperties.getTraces().getSamplerConfig().getArg()));
                    return config;
                }).addTracerProviderCustomizer((provider,config)->{
                    provider.addSpanProcessor(processor);//手动添加处理器
                    return provider;
                })
                .build().getOpenTelemetrySdk();
        return openTelemetry;*/
    }

    @Bean
    @ConditionalOnMissingBean
    @DependsOn("openTelemetry") // 关键：确保openTelemetry先初始化
    public Tracer tracer(OpenTelemetry openTelemetry){
        return openTelemetry.getTracer(environment.getProperty("spring.application.name"));
    }

    /**
     * mvc 的拦截器 controller 就可以子自动生成trace 透传trace
     * @param openTelemetry
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    @DependsOn("openTelemetry")
    public FilterRegistrationBean<Filter> otelFilter(OpenTelemetry openTelemetry) {
        Filter filter = SpringWebMvcTelemetry.create(openTelemetry).createServletFilter();
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean();
        registration.setFilter(filter);
        registration.setOrder(Integer.MIN_VALUE);
        registration.addUrlPatterns(new String[]{"/*"});
        return registration;
    }


}
